package com.zp.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.zp.domain.entity.User;
import com.zp.enums.PictureEditActionEnum;
import com.zp.enums.PictureEditMessageTypeEnum;
import com.zp.manager.disruptor.PictureEditEventProducer;
import com.zp.service.UserService;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 配置 WebSocket 处理器, 处理文本消息
 */
@Component
public class PictureEditHandler extends TextWebSocketHandler {
    @Resource
    private UserService userService;

    @Resource
    private PictureEditEventProducer producer;
    /**
     * 每张图片的编辑状态
     * key: pictureId, value: userId(正在编辑图片的用户 id)
     */
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    /**
     * 保存所有连接的会话
     * key: pictureId, value: 用户会话集合
     */
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();

    /**
     * 连接建立成功后触发
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 1. 保存用户到会话中(记录用户进入直播间)
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 如果 Map 中没有 pictureId, 创建一个线程安全的 Set<WebSocketSession>
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        Set<WebSocketSession> sessions = pictureSessions.get(pictureId);
        sessions.add(session);
        // 2. 构造响应消息(xxx 进入直播间), 展示在前端
        PictureEditResponseMessage message = PictureEditResponseMessage.builder()
                .type(PictureEditMessageTypeEnum.INFO.getValue())
                .message(String.format("用户 %s 加入编辑", user.getUserName()))
                .user(userService.getUserVO(user))
                .build();
        // 3. 广播给所有人, 包括自己
        this.broadcastToPicture(pictureId, message);
    }

    /**
     * 接收到消息时触发
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 将前端传来的消息转换为 PictureEditRequestMessage
        PictureEditRequestMessage requestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        // 根据不同的操作执行不同的逻辑
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 生产消息
        producer.publishEvent(requestMessage, session, user, pictureId);
    }

    /**
     * 进入编辑状态
     */
    public void handleEnterEditMessage(User user, Long pictureId) throws IOException {
        // 没有用户正在编辑, 才能进入编辑状态
        if (!pictureEditingUsers.containsKey(pictureId)) {
            // 设置用户正在编辑图片
            pictureEditingUsers.put(pictureId, user.getId());
            // 构造响应(xxx 开始编辑图片)
            PictureEditResponseMessage responseMessage = PictureEditResponseMessage.builder()
                    .type(PictureEditMessageTypeEnum.ENTER_EDIT.getValue())
                    .message(String.format("用户 %s 开始编辑图片", user.getUserName()))
                    .user(userService.getUserVO(user))
                    .build();
            // 广播给所有用户, 包括自己
            this.broadcastToPicture(pictureId, responseMessage);
        }
    }

    /**
     * 执行编辑操作
     */
    public void handleEditActionMessage(PictureEditRequestMessage requestMessage, WebSocketSession session, User user, Long pictureId) throws IOException {
        // 获取正在编辑的用户
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 获取操作类型
        String editAction = requestMessage.getEditAction();
        PictureEditActionEnum editActionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        // 确定是当前图片的编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 构造响应, xxx用户 正在执行 xxx 操作
            PictureEditResponseMessage message = PictureEditResponseMessage.builder()
                    .type(PictureEditMessageTypeEnum.EDIT_ACTION.getValue())
                    .message(String.format("%s正在执行%s", user.getUserName(), editActionEnum.getText()))
                    .editAction(editAction)
                    .user(userService.getUserVO(user))
                    .build();
            // 广播给所有图片的编辑者, 除了自己, 避免重复编辑
            broadcastToPicture(pictureId, message, session);
        }
    }

    /**
     * 退出编辑状态
     */
    public void handleExitEditMessage(WebSocketSession session, User user, Long pictureId) throws IOException {
        // 获取当前正在编辑的用户
        Long editingUserId = pictureEditingUsers.get(pictureId);
        // 确认是当前的编辑用户
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除当前正在编辑的用户
            pictureEditingUsers.remove(pictureId);
            // 构造响应(用户 xxx 退出编辑)
            PictureEditResponseMessage responseMessage = PictureEditResponseMessage.builder()
                    .type(PictureEditMessageTypeEnum.EXIT_EDIT.getValue())
                    .message(String.format("用户 %s 退出编辑", user.getUserName()))
                    .user(userService.getUserVO(user))
                    .build();
            // 广播给所有用户, 包括自己
            this.broadcastToPicture(pictureId, responseMessage);
        }
    }


    /**
     * 关闭连接时触发
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        User user = (User) session.getAttributes().get("user");
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        // 移除当前用户的编辑状态
        handleExitEditMessage(session, user, pictureId);
        // 删除会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (sessionSet != null) {
            sessionSet.remove(session);
            if (sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }
        // 通知其他用户, 该用户退出编辑
        PictureEditResponseMessage message = PictureEditResponseMessage.builder()
                .type(PictureEditMessageTypeEnum.INFO.getValue())
                .message(String.format("用户 %s 退出编辑", user.getUserName()))
                .user(userService.getUserVO(user))
                .build();
        // 广播给所有用户
        this.broadcastToPicture(pictureId, message);
    }

    /**
     * 广播给进入编辑状态的当前图片的所有用户(支持排除某个用户)
     *
     * @param pictureId       图片 id
     * @param responseMessage 图片编辑响应消息
     * @param excludeSession  排除的 session(某个用户)
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage responseMessage, WebSocketSession excludeSession) throws IOException {
        // 用户会话集合
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if (CollUtil.isNotEmpty(webSocketSessions)) {
            // 创建 ObjectMapper
            ObjectMapper objectMapper = new ObjectMapper();
            // 配置序列化, 将 Long 类型转为 String, 解决丢失精度问题
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance); // 支持 long 基本类型
            objectMapper.registerModule(module);
            // 序列化为 JSON 字符串, 相当于 JSONUtil.toJsonStr(responseMessage), 使用 objectMapper 进行序列化(因为我们解决精度就是这个, 不能使用 Hutool)
            String message = objectMapper.writeValueAsString(responseMessage);
            TextMessage textMessage = new TextMessage(message);
            for (WebSocketSession session : webSocketSessions) {
                // 排除掉某个用户不发送
                if (excludeSession != null && session.equals(excludeSession)) {
                    continue;
                }
                if (session.isOpen()) {
                    session.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 广播给进入编辑状态的当前图片的所有用户
     *
     * @param pictureId       图片 id
     * @param responseMessage 图片编辑响应消息
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage responseMessage) throws IOException {
        this.broadcastToPicture(pictureId, responseMessage, null);
    }
}

